//==============================================================================================================
//	|                        >>>>>>>>>>>>>>>>>> LIBRARIES <<<<<<<<<<<<<<<<<<			|
//==============================================================================================================
#include "config.h"
//------------------------------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//------------------------------------------------------------------------------------------------------------
#include "define_constructions.h"
#include "cpu_consts.h"
#include "ex_mem_t.h"
#include "stack_t.h"
//==============================================================================================================
//==============================================================================================================

//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//	|		        >>>>>>>>>>>>>>>>>> GLOBAL VARIABLES <<<<<<<<<<<<<<<<<<			|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
int	Left_indent = 0;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//	|		 	  >>>>>>>>>>>>>>>>>> STRUCTURES <<<<<<<<<<<<<<<<<<			|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct cpu_mem_t
{
  int            *data;
  int		  data_size;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//	|		   >>>>>>>>>>>>>>>>>> FUNCTION DEFINITION <<<<<<<<<<<<<<<<<<			|
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
void			out_int 			(const int value);
void			out_std_chr			(const int chr_num);
int			in_int				();
//------------------------------------------------------------------------------------------------------------
#define 		WRITE_CHAR_IN_MEM(chr, mem) 	 *mem++ = chr;
#define			WRITE_ARR_IN_MEM(arr, size, mem) for (unsigned i = 0; i < size; i++) *mem++ = arr[i];
//------------------------------------------------------------------------------------------------------------
cpu_mem_t 		*emf_load_program 		(FILE *source);
char                    *emf_wr_info_little_endian      (void *info, char *target_place);
char                    *emf_mk_func_call               (void *func_ptr, char *target_place);
char 			*build_excute 			(char *code_ptr, const int max_size,
                                                         const cpu_mem_t *cpu_code);
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&


//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//	|		   >>>>>>>>>>>>>>>>>> PROGRAM CODE <<<<<<<<<<<<<<<<<<			|
//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


//==============================================================================================================
int main (int argc, char * argv[])
{
  OUT ( "# %s \n"
        "# (%s)\n"
        "# %s %s:\n",
        argv[0], __FILE__, __DATE__, __TIME__ );

  FILE *fin = NULL;

  if (argc >= 2 && argv[1])
    {
      fin = fopen (argv[1], "r");
    }
  else
    {
      printf ("You haven't provided a file to process as a launch argument, enter it, please:\n"
              ">> ");
      char buffer[255] = {0};
      scanf ("%s", buffer);
      fin = fopen (buffer, "r");
    }

  if (!fin)
    {
      printf ("Bad filename! Try again next time.\n");
      return -1;
    }

  cpu_mem_t *cpu_prg = emf_load_program (fin);
  if (!cpu_prg) return -1;

  fclose (fin);
  fin = NULL;

  int opt_cmd_eq_size = 0;
  if (argv[2])
    {
      sscanf (argv[2], "%d", &opt_cmd_eq_size);
    }
  if (!opt_cmd_eq_size)
    {
      printf ("You haven't provided an optimal size of byte equivalent of command as a launch argument. It was chosen automaticaly(20).\n");
      opt_cmd_eq_size = 20;
    }

  const int extremum_prg_size = cpu_prg->data_size * opt_cmd_eq_size + CPU_STACK_SIZE * sizeof(int);
  /*
   Область памяти состоит из области непоредственно для записи и исполнения машинных инструкций
   и из стека, указатель места для записи которого движется с конца к началу.
   *20 - средний размер эквивалента команды
  */

  ex_mem_t *file_memory = ex_mem_constructor (extremum_prg_size);
  if (!file_memory) return -2;

  char *code_ptr = (char *)file_memory->data;

  bool build_excute_ok = (build_excute (code_ptr, extremum_prg_size, cpu_prg) != NULL);
  if (!build_excute_ok) return -3;

  ex_mem_dump (file_memory, stdout);

  printf ("\n"
          "Execute an ilab_cpu program...\n");
  bool ex_mem_execute_ok = (ex_mem_execute (file_memory) == 0);
  if (!ex_mem_execute_ok) return -4;

  ex_mem_dump (file_memory, stdout);

  ex_mem_destructor (file_memory);
  free (cpu_prg -> data);
  free (cpu_prg);

  OUT ("# Success. Will return 0\n");
  return 0;
}


//==============================================================================================================
cpu_mem_t *emf_load_program (FILE *source)
{
  INCR_INDENT;
  INDENT (OUT ("# called cpu_load_program\n"));

  assert (source);

  cpu_mem_t *cpu_prg = (cpu_mem_t *)calloc (1, sizeof (cpu_mem_t));
  assert (cpu_prg);

  cpu_prg -> data = (int *)calloc (CPU_MEMORY_SIZE, sizeof(int));
  assert (cpu_prg -> data);

  int buffer = 0;

  fscanf (source, "%d", &buffer); // signature
  if (buffer != SHCHERBATOV_KIRILL_PROGRAM_SIGNATURE)
    {
      printf ("\n\n"
              "Incorrect program signature!\n\n");
      return NULL;
    }

  fscanf (source, "%d", &buffer); // program size
  if ((unsigned int)buffer > CPU_MEMORY_SIZE)
    {
      printf ("\n\n"
              "This program were not supportable on ilab_cpu: it's too long\n\n");
      return NULL;
    }

  cpu_prg -> data_size = buffer;

  for (register int i = 0; i < buffer; i++)
    fscanf (source, "%d", &(cpu_prg -> data[i]));

  DECR_INDENT;

  return cpu_prg;
}

//==============================================================================================================
char *build_excute (char *code_ptr, const int max_size, const cpu_mem_t *cpu_code)
{
  INCR_INDENT;
  INDENT (OUT ("# call build_mem_excute\n"));

  char **real_addr_start = (char **)calloc (cpu_code -> data_size, sizeof (char *));
  assert (real_addr_start);
  char **real_addr_ptr = real_addr_start;

  char *end_mem = code_ptr + max_size - CPU_STACK_SIZE * sizeof (int);

#define CHECK_MEM_AVAIL_FOR(byte_num)							\
  if (code_ptr + byte_num >= end_mem)                                                   \
  {                                                                                     \
  printf ("\n\n"                                                                        \
  "ERROR! No enough avalible memory...\n"                                               \
  "(try call program with greater \"optimal size of byte equivalent\")\n\n");           \
  return NULL;                                                                          \
}


  CHECK_MEM_AVAIL_FOR (14);

  // 8048060:	83 ec 04             	sub    $0x4,%esp
  WRITE_CHAR_IN_MEM (0x83, code_ptr);
  WRITE_CHAR_IN_MEM (0xec, code_ptr);
  WRITE_CHAR_IN_MEM (0x04, code_ptr);

  //8048080:       bf c4 90 04 08          mov    $0x80490c4,%edi
  //8048085:       83 ef 04                sub    $0x4,%edi
  WRITE_CHAR_IN_MEM (0xbf, code_ptr);
  code_ptr = emf_wr_info_little_endian (code_ptr + max_size, code_ptr);// make edi as a stack pointer
  WRITE_CHAR_IN_MEM (0x83, code_ptr);
  WRITE_CHAR_IN_MEM (0xef, code_ptr);
  WRITE_CHAR_IN_MEM (0x04, code_ptr);

  /*
      8048080:       31 c0                   xor    %eax,%eax
      8048082:       31 db                   xor    %ebx,%ebx
      8048084:       31 c9                   xor    %ecx,%ecx
      8048086:       31 d2                   xor    %edx,%edx
    */

  const char cmd_arr_clear_registers[] = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2";
  CHECK_MEM_AVAIL_FOR (sizeof (cmd_arr_clear_registers) - 1);
  WRITE_ARR_IN_MEM (cmd_arr_clear_registers, sizeof (cmd_arr_clear_registers) - 1, code_ptr);

  /* FUNCTION GLOBAL DATA ARRAY
      804809a:       50                      push   %eax
      804809b:       53                      push   %ebx
      804809c:       51                      push   %ecx
      804809d:       52                      push   %edx

      80480ab:       5a                      pop    %edx
      80480ac:       59                      pop    %ecx
      80480ad:       5b                      pop    %ebx
      80480ae:       58                      pop    %eax
    */
  const char cmd_arr_push_registers[] = "\x50\x53\x51\x52",
      cmd_arr_pop_registers[] = "\x5a\x59\x5b\x58";

  int ip = 0;

#define NEXT_IP(cmd_byte_num)  								\
  *real_addr_ptr = (char *)code_ptr;                                                    \
  real_addr_ptr += cmd_byte_num;                                                        \
  ip+=cmd_byte_num;



  stack_t *proc_later_stack = stack_constructor (cpu_code -> data_size);
  if (stack_is_ok (proc_later_stack) != 1)
    {
      printf ("\n\n"
              "Can't create stack to jmp processing...\n\n");
      return NULL;
    }

  INCR_INDENT;
  while (ip  <  cpu_code -> data_size)
    {
      INDENT (OUT ("# translating code, IP = %d, cmd is %d\n", ip, cpu_code -> data [ip]));
      assert (ip >= 0);
      assert (code_ptr <= end_mem);

      switch (cpu_code -> data [ip])
        {
        // end
        case CPU_CMD_HALT:
          {
            CHECK_MEM_AVAIL_FOR (4);
            NEXT_IP (1);

            //8048063:	83 c4 04             	add    $0x4,%esp
            //8048068:  	c3			ret

            const char cmd_arr[] = "\x83\xc4\x04\xc3";
            WRITE_ARR_IN_MEM (cmd_arr, sizeof (cmd_arr) - 1, code_ptr);

            break;
          }

          // ret
        case CPU_CMD_RET:
          {
            CHECK_MEM_AVAIL_FOR (1);
            NEXT_IP (1);

            WRITE_CHAR_IN_MEM (0xc3, code_ptr);

            break;
          }

          // mov	ax, 123
        case CPU_CMD_PUSH_REGISTER:
          {
            CHECK_MEM_AVAIL_FOR (9);
            NEXT_IP (3);

            /*
                    8048081:       b8 78 56 34 12          mov    $0x12345678,%eax
                    8048086:       bb 78 56 34 12          mov    $0x12345678,%ebx
                    804808b:       b9 78 56 34 12          mov    $0x12345678,%ecx
                    8048090:       ba 78 56 34 12          mov    $0x12345678,%edx
            */
            switch (cpu_code -> data[ip-2])
              {
              case AX:
                WRITE_CHAR_IN_MEM (0xb8, code_ptr);
                break;
              case BX:
                WRITE_CHAR_IN_MEM (0xbb, code_ptr);
                break;
              case CX:
                WRITE_CHAR_IN_MEM (0xb9, code_ptr);
                break;
              case DX:
                WRITE_CHAR_IN_MEM (0xba, code_ptr);
                break;
              }

            code_ptr = emf_wr_info_little_endian ((void *)(cpu_code -> data[ip-1]), code_ptr);

            break;
          }

          // push 123
        case CPU_CMD_PUSH_STACK:
          {
            CHECK_MEM_AVAIL_FOR (9);
            NEXT_IP (2);

            /*
            * 804809e:       c7 07 34 12 cd ab       movl   $0xabcd1234,(%edi)
            * 80480a4:       83 ef 04                sub    $0x4,%edi
            */
            WRITE_CHAR_IN_MEM (0xc7, code_ptr);
            WRITE_CHAR_IN_MEM (0x07, code_ptr);
            code_ptr = emf_wr_info_little_endian ((void *)(cpu_code -> data[ip-1]), code_ptr);
            WRITE_CHAR_IN_MEM (0x83, code_ptr);
            WRITE_CHAR_IN_MEM (0xef, code_ptr);
            WRITE_CHAR_IN_MEM (0x04, code_ptr);

            break;
          }

          // pop ax
        case CPU_CMD_PUSH_REGISTER_FROM_STACK:
          {
            CHECK_MEM_AVAIL_FOR (5);
            NEXT_IP (2);

            /*
            * 80480b0:       83 c7 04                add    $0x4,%edi
            * 80480b3:       8b 07                   mov    (%edi),%eax
            * 80480b5:       8b 1f                   mov    (%edi),%ebx
            * 80480b7:       8b 0f                   mov    (%edi),%ecx
            * 80480b9:       8b 17                   mov    (%edi),%edx
            */

            const char cmd_arr[] = "\x83\xc7\x04\x8b";

            WRITE_ARR_IN_MEM (cmd_arr, sizeof (cmd_arr) - 1, code_ptr);

            switch (cpu_code -> data[ip-1])
              {
              case AX:
                WRITE_CHAR_IN_MEM (0x07, code_ptr);
                break;
              case BX:
                WRITE_CHAR_IN_MEM (0x1f, code_ptr);
                break;
              case CX:
                WRITE_CHAR_IN_MEM (0x0f, code_ptr);
                break;
              case DX:
                WRITE_CHAR_IN_MEM (0x17, code_ptr);
                break;
              }

            break;
          }

          // push ax
        case CPU_CMD_PUSH_REGISTER_TO_STACK:
          {
            CHECK_MEM_AVAIL_FOR (5);
            NEXT_IP (2);

            /*
            * 80480ae:       89 07                   mov    %eax,(%edi)
            * 80480b0:       89 1f                   mov    %ebx,(%edi)
            * 80480b2:       89 0f                   mov    %ecx,(%edi)
            * 80480b4:       89 17                   mov    %edx,(%edi)
            * 80480b6:       83 ef 04                sub    $0x4,%edi
            */
            WRITE_CHAR_IN_MEM (0x89, code_ptr);

            switch (cpu_code -> data[ip-1])
              {
              case AX:
                WRITE_CHAR_IN_MEM (0x07, code_ptr);
                break;
              case BX:
                WRITE_CHAR_IN_MEM (0x1f, code_ptr);
                break;
              case CX:
                WRITE_CHAR_IN_MEM (0x0f, code_ptr);
                break;
              case DX:
                WRITE_CHAR_IN_MEM (0x17, code_ptr);
                break;
              }

            const char cmd_arr[] = "\x83\xef\x04";
            WRITE_ARR_IN_MEM (cmd_arr, sizeof (cmd_arr) - 1, code_ptr);

            break;
          }

          // dub
        case CPU_CMD_DUB:
          {
            CHECK_MEM_AVAIL_FOR (8);
            NEXT_IP (1);


            /*
            * 80480a5:       8b 77 04                mov    0x4(%edi),%esi
            * 80480a8:       89 37                   mov    %esi,(%edi)
            * 80480aa:       83 ef 04                sub    $0x4,%edi
            */
            const char cmd_arr[] = "\x8b\x77\x04\x89\x37\x83\xef\x04";
            WRITE_ARR_IN_MEM (cmd_arr, sizeof (cmd_arr) - 1, code_ptr);

            break;
          }

          // sum
        case CPU_CMD_SUM:
          {
            CHECK_MEM_AVAIL_FOR (11);
            NEXT_IP (1);

            /*
            * 804809a:       83 c7 04                add    $0x4,%edi
            * 804809d:       8b 37                   mov    (%edi),%esi
            * 804809f:       03 77 04                add    0x4(%edi),%esi
            * 80480a2:       89 77 04                mov    %esi,0x4(%edi)
            */
            const char cmd_arr[] = "\x83\xc7\x04\x8b\x37\x03\x77\x04\x89\x77\x04";
            WRITE_ARR_IN_MEM (cmd_arr, sizeof (cmd_arr) - 1, code_ptr);

            break;
          }

          // sub
        case CPU_CMD_SUB:
          {
            CHECK_MEM_AVAIL_FOR (11);
            NEXT_IP (1);

            /*
            * 804809a:       83 c7 04                add    $0x4,%edi
            * 804809d:       8b 37                   mov    (%edi),%esi
            * 804809f:       2b 77 04                sub    0x4(%edi),%esi
            * 80480a2:       89 77 04                mov    %esi,0x4(%edi)
            */
            const char cmd_arr[] = "\x83\xc7\x04\x8b\x37\x2b\x77\x04\x89\x77\x04";
            WRITE_ARR_IN_MEM (cmd_arr, sizeof (cmd_arr) - 1, code_ptr);

            break;
          }

          // mul
        case CPU_CMD_MUL:
          {
            CHECK_MEM_AVAIL_FOR (21);
            NEXT_IP (1);

            /* push register
            * 804809e:       83 c7 04                add    $0x4,%edi
            * 80480a1:       8b 07                   mov    (%edi),%eax
            * 80480a3:       8b 77 04                mov    0x4(%edi),%esi
            * 80480a6:       f7 e6                   mul    %esi
            * 80480a8:       89 47 04                mov    %eax,0x4(%edi)
            * pop resisters
            */

            WRITE_ARR_IN_MEM (cmd_arr_push_registers, sizeof (cmd_arr_push_registers) - 1, code_ptr);

            const char cmd_arr[] = "\x83\xc7\x04\x8b\x07\x8b\x77\x04\xf7\xe6\x89\x47\x04";
            WRITE_ARR_IN_MEM (cmd_arr, sizeof (cmd_arr) - 1, code_ptr);

            WRITE_ARR_IN_MEM (cmd_arr_pop_registers, sizeof (cmd_arr_pop_registers) - 1, code_ptr);
            break;
          }

          // div
        case CPU_CMD_DIV:
          {
            CHECK_MEM_AVAIL_FOR (23);
            NEXT_IP (1);

            /* push register
            * 804809e:       83 c7 04                add    $0x4,%edi
            * 80480a1:       8b 07                   mov    (%edi),%eax
            * 80480a3:       8b 77 04                mov    0x4(%edi),%esi
            * 8048081:       31 d2                   xor    %edx,%edx
            * 80480a6:       f7 f6                   div    %esi
            * 80480a8:       89 47 04                mov    %eax,0x4(%edi)
            * pop resisters
            */

            WRITE_ARR_IN_MEM (cmd_arr_push_registers, sizeof (cmd_arr_push_registers) - 1, code_ptr);

            const char cmd_arr[] = "\x83\xc7\x04\x8b\x07\x8b\x77\x04\x31\xd2\xf7\xf6\x89\x47\x04";
            WRITE_ARR_IN_MEM (cmd_arr, sizeof (cmd_arr) - 1, code_ptr);

            WRITE_ARR_IN_MEM (cmd_arr_pop_registers, sizeof (cmd_arr_pop_registers) - 1, code_ptr);
            break;
          }

          // out
        case CPU_CMD_OUT_VALUE_FROM_STACK:
          {
            CHECK_MEM_AVAIL_FOR (31);
            NEXT_IP (1);

            /*
            get info:
            8048094:       83 c7 04                add    $0x4,%edi
            8048097:       8b 37                   mov    (%edi),%esi
            push registers []
            8048098:       57                      push   %edi
            8048099:       83 ec 04                sub    $0x4,%esp
            804809c:       89 34 24                mov    %esi,(%esp)
            call
            804809d:       83 c4 04                add    $0x4,%esp
            80480a0:       5f                      pop    %edi
            pop registers
            */

            const char cmd_arr_get_info[] = "\x83\xc7\x04\x8b\x37";
            WRITE_ARR_IN_MEM (cmd_arr_get_info, sizeof (cmd_arr_get_info) - 1, code_ptr);

            WRITE_ARR_IN_MEM (cmd_arr_push_registers, sizeof (cmd_arr_push_registers) - 1, code_ptr);

            const char cmd_arr_send_arg[] = "\x57\x83\xec\x04\x89\x34\x24";
            WRITE_ARR_IN_MEM (cmd_arr_send_arg, sizeof (cmd_arr_send_arg) - 1, code_ptr);

            code_ptr = emf_mk_func_call ((void *)out_int, code_ptr);

            const char cmd_arr_restore[] = "\x83\xc4\x04\x5f";
            WRITE_ARR_IN_MEM (cmd_arr_restore, sizeof (cmd_arr_restore) - 1, code_ptr);

            WRITE_ARR_IN_MEM (cmd_arr_pop_registers, sizeof (cmd_arr_pop_registers) - 1, code_ptr);
            break;
          }

          // in
        case CPU_CMD_IN:
          {
            CHECK_MEM_AVAIL_FOR (18);
            NEXT_IP (1);

            const char cmd_arr_push_bx_cx[] = "\x53\x51\x52",
                cmd_arr_pop_bx_cx[] = "\x5a\x59\x5b";

            WRITE_ARR_IN_MEM (cmd_arr_push_bx_cx, sizeof (cmd_arr_push_bx_cx) - 1, code_ptr);

            code_ptr = emf_mk_func_call ((void *)in_int, code_ptr);

            /*
            * 80480ae:       89 07                   mov    %eax,(%edi)
            * 80480b6:       83 ef 04                sub    $0x4,%edi
            */

            const char cmd_arr[] = "\x89\x07\x83\xef\x04";
            WRITE_ARR_IN_MEM (cmd_arr,  sizeof (cmd_arr) - 1, code_ptr);

            WRITE_ARR_IN_MEM (cmd_arr_pop_bx_cx, sizeof (cmd_arr_pop_bx_cx) - 1, code_ptr);

            break;
          }

          // out 0
        case CPU_CMD_OUT_TEXT:
          {
            CHECK_MEM_AVAIL_FOR (30);
            NEXT_IP (2);

            /*
            push registers
            8048098:       57                      push   %edi
            8048099:       83 ec 04                sub    $0x4,%esp

            8048088:       c7 04 24 78 56 34 12    movl   $0x12345678,(%esp)
            call
            804809d:       83 c4 04                add    $0x4,%esp
            80480a0:       5f                      pop    %edi
            pop registers
            */

            WRITE_ARR_IN_MEM (cmd_arr_push_registers, sizeof (cmd_arr_push_registers) - 1, code_ptr);

            const char cmd_arr_send_arg[] = "\x57\x83\xec\x04\xc7\x04\x24";
            WRITE_ARR_IN_MEM (cmd_arr_send_arg, sizeof (cmd_arr_send_arg) - 1, code_ptr);

            code_ptr = emf_wr_info_little_endian ((void *)cpu_code -> data[ip - 1], code_ptr);

            code_ptr = emf_mk_func_call ((void *)out_std_chr, code_ptr);

            const char cmd_arr_restore[] = "\x83\xc4\x04\x5f";
            WRITE_ARR_IN_MEM (cmd_arr_restore, sizeof (cmd_arr_restore) - 1, code_ptr);

            WRITE_ARR_IN_MEM (cmd_arr_pop_registers, sizeof (cmd_arr_pop_registers) - 1, code_ptr);
            break;
          }

          // jmp 1
        case CPU_CMD_JMP:
        case CPU_CMD_CALL:
          {
            CHECK_MEM_AVAIL_FOR (5);
            NEXT_IP (2);

            /*
            8048094:	e9 b5 00 00 00       	jmp    804814e <end>
            8048099:	e8 b0 00 00 00       	call   804814e <end>
            */

            if (cpu_code -> data[ip-2] == CPU_CMD_JMP) WRITE_CHAR_IN_MEM (0xe9, code_ptr)
                else WRITE_CHAR_IN_MEM (0xe8, code_ptr);

            if (stack_push (proc_later_stack, (int)code_ptr) != 0) return NULL;
            if (stack_push (proc_later_stack, cpu_code -> data[ip - 1]) != 0) return NULL;

            code_ptr += ADDR_BYTE_NUM;

            break;
          }

          // je 1
        case CPU_CMD_JE:
        case CPU_CMD_JNE:
        case CPU_CMD_JA:
        case CPU_CMD_JAE:
        case CPU_CMD_JB:
        case CPU_CMD_JBE:
          {
            CHECK_MEM_AVAIL_FOR (22);
            NEXT_IP (2);

            /*
            8048080:       50                      push   %eax
            8048081:       53                      push   %ebx

            8048087:       83 c7 04                add    $0x4,%edi
            804808a:       8b 1f                   mov    (%edi),%ebx

            8048082:       83 c7 04                add    $0x4,%edi
            8048085:       8b 07                   mov    (%edi),%eax

            804808c:       39 c3                   cmp    %eax,%ebx
            804808e:       5b                      pop    %ebx
            804808f:       58                      pop    %eax

            80482c6:       0f 85 b4 fd ff ff       jne    8048080 <_start>
            80482cc:       0f 84 ae fd ff ff       je     8048080 <_start>

            8048094:       0f 8f c2 00 00 00    	jg     804815c <end> ja
            804809a:       0f 8d bc 00 00 00    	jge    804815c <end>
            80480a0:       0f 8c b6 00 00 00    	jl     804815c <end> jb
            80480a6:       0f 8e b0 00 00 00    	jle    804815c <end>
            */

            const char cmd_arr_pre[] = "\x50\x53\x83\xc7\x04\x8b\x1f\x83\xc7\x04\x8b\x07\x39\xc3\x5b\x58\x0f";
            WRITE_ARR_IN_MEM (cmd_arr_pre, sizeof (cmd_arr_pre) - 1, code_ptr);

            switch (cpu_code -> data[ip-2])
              {
              case CPU_CMD_JNE:
                WRITE_CHAR_IN_MEM (0x85, code_ptr);
                break;
              case CPU_CMD_JE:
                WRITE_CHAR_IN_MEM (0x84, code_ptr);
                break;
              case CPU_CMD_JA: // jg
                WRITE_CHAR_IN_MEM (0x8f, code_ptr);
                break;
              case CPU_CMD_JAE: // jge
                WRITE_CHAR_IN_MEM (0x8d, code_ptr);
                break;
              case CPU_CMD_JB: // jl
                WRITE_CHAR_IN_MEM (0x8c, code_ptr);
                break;
              case CPU_CMD_JBE: // jle
                WRITE_CHAR_IN_MEM (0x8e, code_ptr);
                break;
              }

            if (stack_push (proc_later_stack, (int)code_ptr) != 0) return NULL;
            if (stack_push (proc_later_stack, cpu_code -> data[ip - 1]) != 0) return NULL;

            code_ptr += ADDR_BYTE_NUM;

            break;
          }

        default:
          {
            printf ("\n\n"
                    "ERROR! ip = %d, cmd = %d; not implemented yet =C\n\n",
                    ip, cpu_code -> data [ip]);

            return NULL;
          }
        }
    }
  DECR_INDENT;

  CHECK_MEM_AVAIL_FOR (1);
  *code_ptr++ = 0xc3;

  while (!stack_is_empty (proc_later_stack))
    {
      int  cpu_jmp_addr = 0;

      int  temp = 0;
      char *target_place = NULL;

      if (stack_pop (proc_later_stack, &cpu_jmp_addr) != 0) return NULL;

      if (stack_pop (proc_later_stack, &temp) != 0) return NULL;
      target_place = (char *)temp;

      void *jmp_offset = (void *)(real_addr_start [cpu_jmp_addr] - (target_place + ADDR_BYTE_NUM));

      emf_wr_info_little_endian (jmp_offset, target_place);
    }

  free (real_addr_start);
  stack_destructor (proc_later_stack);

  DECR_INDENT;
  return code_ptr;
}


//==============================================================================================================
char *emf_wr_info_little_endian (void *info, char *target_place)
// 4 bytes
{
  //assert (info);
  //assert (target_place);

  addr_u info_temp;
  info_temp.addr = info;

  for (int i = 0; i < ADDR_BYTE_NUM; i++)
    *target_place++ = info_temp.addr_byte[i];

  return target_place;
}

//==============================================================================================================
char *emf_mk_func_call (void *func_ptr, char *target_place)
// 7 bytes
{
  //assert (func_ptr);
  //assert (target_place);

  /*
    * 8048080:       be 78 56 34 12          mov    $0x12345678,%esi
    * 8048085:       ff d6                   call   *%esi
    */

  WRITE_CHAR_IN_MEM (0xbe, target_place);

  target_place = emf_wr_info_little_endian (func_ptr, target_place);

  WRITE_CHAR_IN_MEM (0xff, target_place);
  WRITE_CHAR_IN_MEM (0xd6, target_place);

  return target_place;
}

//==============================================================================================================
void out_std_chr (const int chr_num)
{
  if (chr_num < 0 || chr_num > 2)
    printf ("\n"
            "wrong std_char number %d\n",
            chr_num);
  else
    {
      char buff = 0;

      switch (chr_num)
        {
        case 0:
          buff = '\n';
          break;
        case 1:
          buff = ' ';
          break;
        case 2:
          buff = '\t';
          break;
        }

      printf ("%c", buff);
    }
}

//==============================================================================================================
void out_int (const int val)
{
  printf ("%d", val);
}

//==============================================================================================================
int in_int ()
{
  int temp = 0;
  scanf ("%d", &temp);
  return temp;
}
